<script setup lang="ts">
import { ref, computed } from "vue";
import sparkMD5 from "spark-md5";
import axios from "axios";
import { checkFile, uploadFile, mergerFile } from "@/api/modules/upload";
import { ElMessage } from "element-plus";
const CHUNK_SIZE = 8 * 1024 * 1024;

interface Chunk {
  index: number;
  file: Blob;
  time?: number;
}

interface ChunkUpload {
  form: FormData;
  index: number;
  error: number;
}

interface ChunkInfo {
  hash: string;
  name: string;
  index: number;
  chunk: Blob;
  progress: number;
}

const file = ref<any>(null);
const chunks = ref<ChunkInfo[]>([]);
const hashProgress = ref<number>(0);
const hash = ref<string>("");
const name = ref("");
const uploadedProgress = computed(() => {
  if (!file.value || !chunks.value.length) {
    return 0;
  }

  const loaded = chunks.value.reduce((acc, cur) => {
    const chunk_loaded = (cur.progress / 100) * cur.chunk.size;
    return acc + chunk_loaded;
  }, 0);

  const totalSize = chunks.value.reduce((acc, cur) => acc + cur.chunk.size, 0);

  return parseFloat(((loaded * 100) / totalSize).toFixed(2));
});

const cubeWidth = computed(() => {
  return Math.ceil(Math.sqrt(chunks.value.length)) * 20;
});

const handleFileChange = (e: FileList) => {
  if (e && e.length > 0) {
    file.value = e[0];
  }
};

const handleFileUpload = async (e: any) => {
  if (!file) {
    return;
  }
  file.value = e.file;
  name.value = file.value.name;
  await upload();
};

const upload = async () => {
  // 切片
  const chunkArray = createFileChunk(file.value);
  // 哈希
  const calculatedHash = await calculateHash(chunkArray);

  hash.value = calculatedHash;
  // 查询是否已上传

  const res: any = await checkFile({
    hash: calculatedHash,
    ext: name.value?.split(".").pop()
  });

  if (!res || !res.data) {
    return;
  }
  const { uploaded, uploadedList } = res.data;
  if (uploaded) {
    /* return window.alert("秒传成功"); */
    ElMessage.success('秒传成功')
  }

  chunks.value = chunkArray.map((chunk, index) => {
    const name = calculatedHash + "-" + index;
    const isChunkUploaded = uploadedList.includes(name);
    return {
      hash: calculatedHash,
      name,
      index,
      chunk: chunk.file,
      progress: isChunkUploaded ? 100 : 0
    };
  });
  console.log(chunks.value);
  // 上传没有哈希计算的切片
  uploadChunks(uploadedList);
};

const uploadChunks = async (uploadedList: string[]) => {
  const requests: ChunkUpload[] = chunks.value
    .filter(chunk => !uploadedList.includes(chunk.name))
    .map((chunk, index) => {
      const form = new FormData();
      form.append("chunk", chunk.chunk);
      form.append("hash", chunk.hash);
      form.append("name", chunk.name);
      return { form, index: chunk.index, error: 0 };
    });

  const sendRequest = async (limit = 1, task: Promise<void>[] = []) => {
    let count = 0;
    let isStop = false;
    const len = requests.length;

    const upLoadReq = async () => {
      if (isStop) {
        return;
      }
      const req = requests.shift();
      if (!req) {
        return;
      }
      const { form, index } = req;
      try {
        const res: any = await uploadFile(form, {
          onUploadProgress: (progress: any) => {
            chunks.value[index].progress = Number(((progress.loaded / progress.total) * 100).toFixed(2));
          }
        });

        // 最后一片
        if (count == len - 1) {
          return;
        } else {
          count++;
          await upLoadReq();
        }
      } catch (err) {
        chunks.value[index].progress = -1;
        if (req.error < 3) {
          req.error++;
          requests.unshift(req);
          await upLoadReq();
        } else {
          isStop = true;
          throw new Error("Upload failed");
        }
      }
    };

    while (limit > 0) {
      task.push(upLoadReq());
      limit--;
    }
    await Promise.all(task);
  };

  await sendRequest(3);
  mergeFile();
};

const mergeFile = async () => {
  await mergerFile({
    ext: name.value.split(".").pop(),
    size: CHUNK_SIZE,
    hash: hash.value
  }).then(res => {
    if (res && res.data) {
      console.log(res.data);
    }
  });
};

const createFileChunk = (file: File): Chunk[] => {
  const chunks: Chunk[] = [];
  let cur = 0;
  const maxLen = Math.ceil(file.size / CHUNK_SIZE);
  while (cur < maxLen) {
    const start = cur * CHUNK_SIZE;
    const end = start + CHUNK_SIZE >= file.size ? file.size : start + CHUNK_SIZE;
    chunks.push({ index: cur, file: file.slice(start, end) });
    cur++;
  }
  return chunks;
};

const calculateHash = (chunks: Chunk[]): Promise<string> => {
  const spark = new sparkMD5.ArrayBuffer();
  let count = 0;
  const len = chunks.length;
  let hash: string;
  const startTime = new Date().getTime();
  return new Promise(resolve => {
    const loadNext = (index: number) => {
      const reader = new FileReader();
      reader.readAsArrayBuffer(chunks[index].file);
      reader.onload = function (e) {
        const endTime = new Date().getTime();
        chunks[count] = { ...chunks[count], time: endTime - startTime };
        count++;
        spark.append(e.target!.result as ArrayBuffer);
        if (count == len) {
          hashProgress.value = 100;
          hash = spark.end();
          resolve(hash);
        } else {
          hashProgress.value += 100 / len;
          loadNext(index + 1);
        }
      };
    };

    loadNext(0);
  });
};
</script>

<template>
  <div class="wrap">
    <el-upload ref="file" :http-request="handleFileUpload" action="#" class="avatar-uploader" :show-file-list="false">
  <el-button type="primary" icon="plus">上传文件</el-button>
</el-upload>
    <div class="bar">
      <div class="progress-text">
        <div>计算hash的进度：</div>
        <el-progress :stroke-width="20" :text-inside="true" :percentage="hashProgress"></el-progress>
      </div>
      <div class="progress-text">
        <div>上传进度：</div>
        <el-progress
          :stroke-width="20"
          :text-inside="true"
          :percentage="uploadedProgress > 100 ? 100 : uploadedProgress"
        ></el-progress>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.wrap {
  width: 400px;
  height: 200px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  .avatar-uploader{
      margin-bottom: 10px;
  }
}

.progress-bar {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-top: 20px;
}

.progress-text {
  line-height: 1.5;
    margin-top: 20px;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    font-weight: 600;
}

.el-progress {
  width: 200px;
  margin-left: 10px;
}
</style>
